/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.designer.util.dialog;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.SelectionDialog;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.StringMatcher;
import org.eclipse.ui.model.WorkbenchLabelProvider;

/**
 * Shows a list of resources to the user with a text entry field
 * for a string pattern used to filter the list of resources.
 * <p>
 *
 * @since 2.1
 */
public class ActivitiResourceSelectionDialog extends SelectionDialog {

  private static final String DIALOG_SETTINGS_SECTION = "ResourceListSelectionDialogSettings"; //$NON-NLS-1$

    Text pattern;

    Table resourceNames;

    Table folderNames;

    String patternString;

    String initialPattern;

    IContainer container;

    int typeMask;

    private static Collator collator = Collator.getInstance();

    boolean gatherResourcesDynamically = true;

    StringMatcher stringMatcher;

    UpdateFilterThread updateFilterThread;

    UpdateGatherThread updateGatherThread;

    ResourceDescriptor[] descriptors;

    int descriptorsSize;

    WorkbenchLabelProvider labelProvider = new WorkbenchLabelProvider();

    boolean okEnabled = false;

    private boolean showDerived = false;

    private Button showDerivedButton;

    private boolean allowUserToToggleDerived;

    static class ResourceDescriptor implements Comparable {
        String label;

        ArrayList resources = new ArrayList();

        boolean resourcesSorted = true;

        @Override
        public int compareTo(Object o) {
            return collator.compare(label, ((ResourceDescriptor) o).label);
        }
    }

    class UpdateFilterThread extends Thread {
        boolean stop = false;

        int firstMatch = 0;

        int lastMatch = descriptorsSize - 1;

        @Override
        public void run() {
            Display display = resourceNames.getDisplay();
            final int itemIndex[] = { 0 };
            final int itemCount[] = { 0 };
            //Keep track of if the widget got disposed
            //so that we can abort if required
            final boolean[] disposed = { false };
            display.syncExec(new Runnable() {
                @Override
                public void run() {
                    //Be sure the widget still exists
                    if (resourceNames.isDisposed()) {
                        disposed[0] = true;
                        return;
                    }
                    itemCount[0] = resourceNames.getItemCount();
                }
            });

            if (disposed[0]) {
        return;
      }

            int last;
            if (patternString.indexOf('?') == -1
                    && patternString.endsWith("*") && //$NON-NLS-1$
                    patternString.indexOf('*') == patternString.length() - 1) {
                // Use a binary search to get first and last match when the pattern
                // string ends with "*" and has no other embedded special characters.
                // For this case, we can be smarter about getting the first and last
                // match since the items are in sorted order.
                firstMatch = getFirstMatch();
                if (firstMatch == -1) {
                    firstMatch = 0;
                    lastMatch = -1;
                } else {
                    lastMatch = getLastMatch();
                }
                last = lastMatch;
                for (int i = firstMatch; i <= lastMatch; i++) {
                    if (i % 50 == 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            // ignore
                        }
                    }
                    if (stop || resourceNames.isDisposed()) {
                        disposed[0] = true;
                        return;
                    }
                    final int index = i;
                    display.syncExec(new Runnable() {
                        @Override
                        public void run() {
                            if (stop || resourceNames.isDisposed()) {
                return;
              }
                            updateItem(index, itemIndex[0], itemCount[0]);
                            itemIndex[0]++;
                        }
                    });
                }
            } else {
                last = lastMatch;
                boolean setFirstMatch = true;
                for (int i = firstMatch; i <= lastMatch; i++) {
                    if (i % 50 == 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            // ignore
                        }
                    }
                    if (stop || resourceNames.isDisposed()) {
                        disposed[0] = true;
                        return;
                    }
                    final int index = i;
                    if (match(descriptors[index].label)) {
                        if (setFirstMatch) {
                            setFirstMatch = false;
                            firstMatch = index;
                        }
                        last = index;
                        display.syncExec(new Runnable() {
                            @Override
                            public void run() {
                                if (stop || resourceNames.isDisposed()) {
                  return;
                }
                                updateItem(index, itemIndex[0], itemCount[0]);
                                itemIndex[0]++;
                            }
                        });
                    }
                }
            }

            if (disposed[0]) {
        return;
      }

            lastMatch = last;
            display.syncExec(new Runnable() {
                @Override
                public void run() {
                    if (resourceNames.isDisposed()) {
            return;
          }
                    itemCount[0] = resourceNames.getItemCount();
                    if (itemIndex[0] < itemCount[0]) {
                        resourceNames.setRedraw(false);
                        resourceNames.remove(itemIndex[0], itemCount[0] - 1);
                        resourceNames.setRedraw(true);
                    }
                    // If no resources, remove remaining folder entries
                    if (resourceNames.getItemCount() == 0) {
                        folderNames.removeAll();
                        updateOKState(false);
                    }
                }
            });
        }
    }

    class UpdateGatherThread extends Thread {
        boolean stop = false;

        int lastMatch = -1;

        int firstMatch = 0;

        boolean refilter = false;

        @Override
        public void run() {
            Display display = resourceNames.getDisplay();
            final int itemIndex[] = { 0 };
            final int itemCount[] = { 0 };
            //Keep track of if the widget got disposed
            //so that we can abort if required
            final boolean[] disposed = { false };
            display.syncExec(new Runnable() {
                @Override
                public void run() {
                    //Be sure the widget still exists
                    if (resourceNames.isDisposed()) {
                        disposed[0] = true;
                        return;
                    }
                    itemCount[0] = resourceNames.getItemCount();
                }
            });

            if (disposed[0]) {
                return;
            }

            if (!refilter) {
                for (int i = 0; i <= lastMatch; i++) {
                    if (i % 50 == 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            // ignore
                        }
                    }
                    if (stop || resourceNames.isDisposed()) {
                        disposed[0] = true;
                        return;
                    }
                    final int index = i;
                    display.syncExec(new Runnable() {
                        @Override
                        public void run() {
                            if (stop || resourceNames.isDisposed()) {
                return;
              }
                            updateItem(index, itemIndex[0], itemCount[0]);
                            itemIndex[0]++;
                        }
                    });
                }
            } else {
                // we're filtering the previous list
                for (int i = firstMatch; i <= lastMatch; i++) {
                    if (i % 50 == 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            // ignore
                        }
                    }
                    if (stop || resourceNames.isDisposed()) {
                        disposed[0] = true;
                        return;
                    }
                    final int index = i;
                    if (match(descriptors[index].label)) {
                        display.syncExec(new Runnable() {
                            @Override
                            public void run() {
                                if (stop || resourceNames.isDisposed()) {
                  return;
                }
                                updateItem(index, itemIndex[0], itemCount[0]);
                                itemIndex[0]++;
                            }
                        });
                    }
                }
            }

            if (disposed[0]) {
                return;
            }

            display.syncExec(new Runnable() {
                @Override
                public void run() {
                    if (resourceNames.isDisposed()) {
                        return;
                    }
                    itemCount[0] = resourceNames.getItemCount();
                    if (itemIndex[0] < itemCount[0]) {
                        resourceNames.setRedraw(false);
                        resourceNames.remove(itemIndex[0], itemCount[0] - 1);
                        resourceNames.setRedraw(true);
                    }
                    // If no resources, remove remaining folder entries
                    if (resourceNames.getItemCount() == 0) {
                        folderNames.removeAll();
                        updateOKState(false);
                    }
                }
            });
        }
    }

    /**
     * Creates a new instance of the class.
     *
     * @param parentShell shell to parent the dialog on
     * @param resources resources to display in the dialog
     */
    public ActivitiResourceSelectionDialog(Shell parentShell, IResource[] resources) {
        super(parentShell);
        gatherResourcesDynamically = false;
        initDescriptors(resources);
    }

    /**
     * Creates a new instance of the class.  When this constructor is used to
     * create the dialog, resources will be gathered dynamically as the pattern
     * string is specified.  Only resources of the given types that match the
     * pattern string will be listed.  To further filter the matching resources,
     * @see #select(IResource)
     *
     * @param parentShell shell to parent the dialog on
     * @param container container to get resources from
     * @param typeMask mask containing IResource types to be considered
     */
    public ActivitiResourceSelectionDialog(Shell parentShell, IContainer container,
            int typeMask) {
        super(parentShell);
        this.container = container;
        this.typeMask = typeMask;
    }

    /**
     * Adjust the pattern string for matching.
     */
    protected String adjustPattern() {
        String text = pattern.getText().trim();
        if (text.endsWith("<")) { //$NON-NLS-1$
            // the < character indicates an exact match search
            return text.substring(0, text.length() - 1);
        }
        if (!text.equals("") && !text.endsWith("*")) { //$NON-NLS-1$ //$NON-NLS-2$
          return text + "*"; //$NON-NLS-1$
        }
        return text;
    }

    /**
     * @see org.eclipse.jface.dialogs.Dialog#cancelPressed()
     */
    @Override
    protected void cancelPressed() {
        setResult(null);
        super.cancelPressed();
    }

    /**
     * @see org.eclipse.jface.window.Window#close()
     */
    @Override
    public boolean close() {
        boolean result = super.close();
        labelProvider.dispose();
        return result;
    }

    /**
     * @see org.eclipse.jface.window.Window#create()
     */
    @Override
    public void create() {
        super.create();
        pattern.setFocus();
        getButton(IDialogConstants.OK_ID).setEnabled(okEnabled);
    }

    /**
     * Creates the contents of this dialog, initializes the
     * listener and the update thread.
     *
     * @param parent parent to create the dialog widgets in
     */
    @Override
    protected Control createDialogArea(Composite parent) {

        Composite dialogArea = (Composite) super.createDialogArea(parent);
        Label l = new Label(dialogArea, SWT.NONE);
        l.setText(IDEWorkbenchMessages.ResourceSelectionDialog_label);
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        l.setLayoutData(data);

        pattern = new Text(dialogArea, SWT.SINGLE | SWT.BORDER);
        pattern.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        l = new Label(dialogArea, SWT.NONE);
        l.setText(IDEWorkbenchMessages.ResourceSelectionDialog_matching);
        data = new GridData(GridData.FILL_HORIZONTAL);
        l.setLayoutData(data);
        resourceNames = new Table(dialogArea, SWT.SINGLE | SWT.BORDER
                | SWT.V_SCROLL);
        data = new GridData(GridData.FILL_BOTH);
        data.heightHint = 12 * resourceNames.getItemHeight();
        resourceNames.setLayoutData(data);

        l = new Label(dialogArea, SWT.NONE);
        l.setText(IDEWorkbenchMessages.ResourceSelectionDialog_folders);
        data = new GridData(GridData.FILL_HORIZONTAL);
        l.setLayoutData(data);

        folderNames = new Table(dialogArea, SWT.SINGLE | SWT.BORDER
                | SWT.V_SCROLL | SWT.H_SCROLL);
        data = new GridData(GridData.FILL_BOTH);
        data.widthHint = 300;
        data.heightHint = 4 * folderNames.getItemHeight();
        folderNames.setLayoutData(data);

        if (gatherResourcesDynamically) {
            updateGatherThread = new UpdateGatherThread();
        } else {
            updateFilterThread = new UpdateFilterThread();
        }

        pattern.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                if (e.keyCode == SWT.ARROW_DOWN) {
          resourceNames.setFocus();
        }
            }
        });

        pattern.addModifyListener(new ModifyListener() {
            @Override
            public void modifyText(ModifyEvent e) {
                refresh(false);
            }
        });

        resourceNames.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                updateFolders((ResourceDescriptor) e.item.getData());
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                okPressed();
            }
        });

        folderNames.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                okPressed();
            }
        });

        if (getAllowUserToToggleDerived()) {
            showDerivedButton = new Button(dialogArea, SWT.CHECK);
            showDerivedButton.setText(IDEWorkbenchMessages.ResourceSelectionDialog_showDerived);
            showDerivedButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    setShowDerived(showDerivedButton.getSelection());
                    refresh(true);
                }
            });
            showDerivedButton.setSelection(getShowDerived());
        }

        applyDialogFont(dialogArea);

        if (initialPattern != null) {
          pattern.setText(initialPattern);
          refresh(false);
        }

        return dialogArea;
    }

    /**
     * Returns whether to include a "Show derived resources" checkbox in the dialog.
     * The default is <code>false</code>.
     *
     * @return <code>true</code> to include the checkbox, <code>false</code> to omit
     * @since 3.1
     */
    public boolean getAllowUserToToggleDerived() {
        return allowUserToToggleDerived;
    }

    /**
     * Sets whether to include a "Show derived resources" checkbox in the dialog.
     *
     * @param allow <code>true</code> to include the checkbox, <code>false</code> to omit
     * @since 3.1
     */
    public void setAllowUserToToggleDerived(boolean allow) {
        allowUserToToggleDerived = allow;
    }

    /**
     */
    private void filterResources(boolean force) {
        String oldPattern = force ? null : patternString;
        patternString = adjustPattern();
        if (!force && patternString.equals(oldPattern)) {
      return;
    }

        updateFilterThread.stop = true;
        stringMatcher = new StringMatcher(patternString, true, false);
        UpdateFilterThread oldThread = updateFilterThread;
        updateFilterThread = new UpdateFilterThread();
        if (patternString.equals("")) { //$NON-NLS-1$
            updateFilterThread.firstMatch = 0;
            updateFilterThread.lastMatch = -1;
            updateFilterThread.start();
            return;
        }

        if (oldPattern != null && oldPattern.length() != 0
                && oldPattern.endsWith("*") && patternString.endsWith("*")) { //$NON-NLS-1$ //$NON-NLS-2$
            int matchLength = oldPattern.length() - 1;
            if (patternString.regionMatches(0, oldPattern, 0, matchLength)) {
                // filter the previous list of items, this is done when the
                // new pattern is a derivative of the old pattern
                updateFilterThread.firstMatch = oldThread.firstMatch;
                updateFilterThread.lastMatch = oldThread.lastMatch;
                updateFilterThread.start();
                return;
            }
        }

        // filter the entire list
        updateFilterThread.firstMatch = 0;
        updateFilterThread.lastMatch = descriptorsSize - 1;
        updateFilterThread.start();
    }

    /**
     * Use a binary search to get the first match for the patternString.
     * This method assumes the patternString does not contain any '?'
     * characters and that it contains only one '*' character at the end
     * of the string.
     */
    private int getFirstMatch() {
        int high = descriptorsSize;
        int low = -1;
        boolean match = false;
        ResourceDescriptor desc = new ResourceDescriptor();
        desc.label = patternString.substring(0, patternString.length() - 1);
        while (high - low > 1) {
            int index = (high + low) / 2;
            String label = descriptors[index].label;
            if (match(label)) {
                high = index;
                match = true;
            } else {
                int compare = descriptors[index].compareTo(desc);
                if (compare == -1) {
                    low = index;
                } else {
                    high = index;
                }
            }
        }
        if (match) {
      return high;
    }
        return -1;
    }

    /**
     */
    private void gatherResources(boolean force) {
        String oldPattern = force ? null : patternString;
        patternString = adjustPattern();
        if (!force && patternString.equals(oldPattern)) {
      return;
    }

        updateGatherThread.stop = true;
        updateGatherThread = new UpdateGatherThread();

        if (patternString.equals("")) { //$NON-NLS-1$
            updateGatherThread.start();
            return;
        }
        stringMatcher = new StringMatcher(patternString, true, false);

        if (oldPattern != null && oldPattern.length() != 0
                && oldPattern.endsWith("*") && patternString.endsWith("*")) { //$NON-NLS-1$ //$NON-NLS-2$
            // see if the new pattern is a derivative of the old pattern
            int matchLength = oldPattern.length() - 1;
            if (patternString.regionMatches(0, oldPattern, 0, matchLength)) {
                updateGatherThread.refilter = true;
                updateGatherThread.firstMatch = 0;
                updateGatherThread.lastMatch = descriptorsSize - 1;
                updateGatherThread.start();
                return;
            }
        }

        final ArrayList resources = new ArrayList();
        BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
            @Override
            public void run() {
                getMatchingResources(resources);
                IResource resourcesArray[] = new IResource[resources.size()];
                resources.toArray(resourcesArray);
                initDescriptors(resourcesArray);
            }
        });

        updateGatherThread.firstMatch = 0;
        updateGatherThread.lastMatch = descriptorsSize - 1;
        updateGatherThread.start();
    }

    /**
     * Return an image for a resource descriptor.
     *
     * @param desc resource descriptor to return image for
     * @return an image for a resource descriptor.
     */
    private Image getImage(ResourceDescriptor desc) {
        IResource r = (IResource) desc.resources.get(0);
        return labelProvider.getImage(r);
    }

    /**
     * Use a binary search to get the last match for the patternString.
     * This method assumes the patternString does not contain any '?'
     * characters and that it contains only one '*' character at the end
     * of the string.
     */
    private int getLastMatch() {
        int high = descriptorsSize;
        int low = -1;
        boolean match = false;
        ResourceDescriptor desc = new ResourceDescriptor();
        desc.label = patternString.substring(0, patternString.length() - 1);
        while (high - low > 1) {
            int index = (high + low) / 2;
            String label = descriptors[index].label;
            if (match(label)) {
                low = index;
                match = true;
            } else {
                int compare = descriptors[index].compareTo(desc);
                if (compare == -1) {
                    low = index;
                } else {
                    high = index;
                }
            }
        }
        if (match) {
      return low;
    }
        return -1;
    }

    /**
     * Gather the resources of the specified type that match the current
     * pattern string.  Gather the resources using the proxy visitor since
     * this is quicker than getting the entire resource.
     *
     * @param resources resources that match
     */
    private void getMatchingResources(final ArrayList resources) {
        try {
            container.accept(new IResourceProxyVisitor() {
                @Override
                public boolean visit(IResourceProxy proxy) {
                    // optionally exclude derived resources (bugs 38085 and 81333)
                    if (!getShowDerived() && proxy.isDerived()) {
                        return false;
                    }
                    int type = proxy.getType();
                    if ((typeMask & type) != 0) {
                        if (match(proxy.getName())) {
                            IResource res = proxy.requestResource();
                            if (select(res)) {
                                resources.add(res);
                                return true;
                            }
                            return false;
                        }
                    }
                    if (type == IResource.FILE) {
            return false;
          }
                    return true;
                }
            }, IResource.NONE);
        } catch (CoreException e) {
            // ignore
        }
    }

    private Image getParentImage(IResource resource) {
        IResource parent = resource.getParent();
        return labelProvider.getImage(parent);
    }

    private String getParentLabel(IResource resource) {
        IResource parent = resource.getParent();
        String text;
        if (parent.getType() == IResource.ROOT) {
            // Get readable name for workspace root ("Workspace"), without duplicating language-specific string here.
            text = labelProvider.getText(parent);
        } else {
            text = parent.getFullPath().makeRelative().toString();
        }
        if(text == null) {
      return ""; //$NON-NLS-1$
    }
        return text;
    }

    /**
     * Returns whether derived resources should be shown in the list.
     * The default is <code>false</code>.
     *
     * @return <code>true</code> to show derived resources, <code>false</code> to hide them
     * @since 3.1
     */
    protected boolean getShowDerived() {
       return showDerived ;
    }

    /**
     * Sets whether derived resources should be shown in the list.
     *
     * @param show <code>true</code> to show derived resources, <code>false</code> to hide them
     * @since 3.1
     */
    protected void setShowDerived(boolean show) {
        showDerived  = show;
    }

    public void setInitialPattern(final String pattern) {
      initialPattern = pattern;
    }

    /**
     * Creates a ResourceDescriptor for each IResource,
     * sorts them and removes the duplicated ones.
     *
     * @param resources resources to create resource descriptors for
     */
    private void initDescriptors(final IResource resources[]) {
        BusyIndicator.showWhile(null, new Runnable() {
            @Override
            public void run() {
                descriptors = new ResourceDescriptor[resources.length];
                for (int i = 0; i < resources.length; i++) {
                    IResource r = resources[i];
                    ResourceDescriptor d = new ResourceDescriptor();
                    //TDB: Should use the label provider and compare performance.
                    d.label = r.getName();
                    d.resources.add(r);
                    descriptors[i] = d;
                }
                Arrays.sort(descriptors);
                descriptorsSize = descriptors.length;

                //Merge the resource descriptor with the same label and type.
                int index = 0;
                if (descriptorsSize < 2) {
          return;
        }
                ResourceDescriptor current = descriptors[index];
                IResource currentResource = (IResource) current.resources
                        .get(0);
                for (int i = 1; i < descriptorsSize; i++) {
                    ResourceDescriptor next = descriptors[i];
                    IResource nextResource = (IResource) next.resources.get(0);
                    if (nextResource.getType() == currentResource.getType()
                            && next.label.equals(current.label)) {
                        current.resources.add(nextResource);
                        // If we are merging resources with the same name, into a single descriptor,
                        // then we must mark the descriptor unsorted so that we will sort the folder
                        // names.
                        // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=76496
                        current.resourcesSorted = false;
                    } else {
                        if (current.resources.size() > 1) {
                            current.resourcesSorted = false;
                        }
                        descriptors[index + 1] = descriptors[i];
                        index++;
                        current = descriptors[index];
                        currentResource = (IResource) current.resources.get(0);
                    }
                }
                descriptorsSize = index + 1;
            }
        });
    }

    /**
     * Returns true if the label matches the chosen pattern.
     *
     * @param label label to match with the current pattern
     * @return true if the label matches the chosen pattern.
     *  false otherwise.
     */
    private boolean match(String label) {
        if (patternString == null
                || patternString.equals("") || patternString.equals("*")) { //$NON-NLS-1$ //$NON-NLS-2$
      return true;
    }
        return stringMatcher.match(label);
    }

    /**
     * The user has selected a resource and the dialog is closing.
     * Set the selected resource as the dialog result.
     */
    @Override
    protected void okPressed() {
        TableItem items[] = folderNames.getSelection();
        if (items.length == 1) {
            ArrayList result = new ArrayList();
            result.add(items[0].getData());
            setResult(result);
        }
        super.okPressed();
    }

    /**
     * Use this method to further filter resources.  As resources are gathered,
     * if a resource matches the current pattern string, this method will be called.
     * If this method answers false, the resource will not be included in the list
     * of matches and the resource's children will NOT be considered for matching.
     */
    protected boolean select(IResource resource) {
        return true;
    }

    /**
     * Refreshes the filtered list of resources.
     * Called when the text in the pattern text entry has changed.
     *
     * @param force if <code>true</code> a refresh is forced, if <code>false</code> a refresh only
     *   occurs if the pattern has changed
     *
     * @since 3.1
     */
    protected void refresh(boolean force) {
        if (gatherResourcesDynamically) {
            gatherResources(force);
        } else {
            filterResources(force);
        }
    }

    /**
     * A new resource has been selected. Change the contents
     * of the folder names list.
     *
     * @desc resource descriptor of the selected resource
     */
    private void updateFolders(final ResourceDescriptor desc) {
        BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
            @Override
            public void run() {
                if (!desc.resourcesSorted) {
                    // sort the folder names
                    Collections.sort(desc.resources, new Comparator() {
                        @Override
                        public int compare(Object o1, Object o2) {
                            String s1 = getParentLabel((IResource) o1);
                            String s2 = getParentLabel((IResource) o2);
                            return collator.compare(s1, s2);
                        }
                    });
                    desc.resourcesSorted = true;
                }
                folderNames.removeAll();
                for (int i = 0; i < desc.resources.size(); i++) {
                    TableItem newItem = new TableItem(folderNames, SWT.NONE);
                    IResource r = (IResource) desc.resources.get(i);
                    newItem.setText(getParentLabel(r));
                    newItem.setImage(getParentImage(r));
                    newItem.setData(r);
                }
                folderNames.setSelection(0);
            }
        });
    }

    /**
     * Update the specified item with the new info from the resource
     * descriptor.
     * Create a new table item if there is no item.
     *
     * @param index index of the resource descriptor
     * @param itemPos position of the existing item to update
     * @param itemCount number of items in the resources table widget
     */
    private void updateItem(int index, int itemPos, int itemCount) {
        ResourceDescriptor desc = descriptors[index];
        TableItem item;
        if (itemPos < itemCount) {
            item = resourceNames.getItem(itemPos);
            if (item.getData() != desc) {
                item.setText(desc.label);
                item.setData(desc);
                item.setImage(getImage(desc));
                if (itemPos == 0) {
                    resourceNames.setSelection(0);
                    updateFolders(desc);
                }
            }
        } else {
            item = new TableItem(resourceNames, SWT.NONE);
            item.setText(desc.label);
            item.setData(desc);
            item.setImage(getImage(desc));
            if (itemPos == 0) {
                resourceNames.setSelection(0);
                updateFolders(desc);
            }
        }
        updateOKState(true);
    }

    /**
     * Update the enabled state of the OK button.  To be called when
     * the resource list is updated.
     * @param state the new enabled state of the button
     */
    protected void updateOKState(boolean state) {
      Button okButton = getButton(IDialogConstants.OK_ID);
      if(okButton != null && !okButton.isDisposed() && state != okEnabled) {
        okButton.setEnabled(state);
        okEnabled = state;
      }
    }


  /* (non-Javadoc)
     * @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings()
     *
     * @since 3.2
     */
  @Override
  protected IDialogSettings getDialogBoundsSettings() {
        IDialogSettings settings = IDEWorkbenchPlugin.getDefault().getDialogSettings();
        IDialogSettings section = settings.getSection(DIALOG_SETTINGS_SECTION);
        if (section == null) {
            section = settings.addNewSection(DIALOG_SETTINGS_SECTION);
        }
        return section;
  }
}



